package com.test.mapper.services.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import com.test.dao.DBOperationResult;
import com.test.dao.SearchCriteria;
import com.test.dao.SearchResultSet;
import com.test.mapper.dao.EntityDao;
import com.test.mapper.dao.EntityPropertyDao;
import com.test.mapper.enitites.EntityModel;
import com.test.mapper.enitites.EntityProperty;
import com.test.mapper.enitites.PropertyType;
import com.test.mapper.enitites.pk.EntityModelPK;
import com.test.mapper.enitites.pk.EntityPropertyPK;
import com.test.mapper.exceptions.DuplicateEntityNameException;
import com.test.mapper.services.EntityPropertyService;
import com.test.mapper.services.EntityService;
import com.test.utils.dao.DBUtils;
import com.test.utils.entities.EntitiesUtils;
import com.test.utils.search.SearchUtils;

public class EntityServiceImpl implements EntityService {

	private EntityDao entityDao;

	private EntityPropertyDao entityPropertyDao;

	private EntityPropertyService entityPropertyService;

	private Logger logger;

	/*
	 * (non-Javadoc)
	 * @see com.test.mapper.services.EntityService#addEntity(com.test.mapper.enitites.EntityModel)
	 */
	public boolean addEntity(final EntityModel entity) {
		logger.debug("START addEntity");

		if (entity == null) {
			throw new NullPointerException("Cannot add NULL entity");
		}

		entity.setEntityName(EntitiesUtils.removeDuplicateSpaces(entity.getEntityName()));
		final boolean nameAvailable = isEntityNameAvailable(entity.getClientId(), entity.getEntityName());
		if (!nameAvailable) {
			throw new DuplicateEntityNameException("Entity name " + entity.getEntityName() + " already exists");
		}
		entity.setTableName(findAvailableTableName(entity.getClientId(),
				DBUtils.generateDBIdentifierFromName(entity.getEntityName())));

		entityPropertyService.generateColumnName(entity.getPrimaryKey());
		final DBOperationResult result = entityDao.add(entity);
		if (result == DBOperationResult.SUCCESS_SINGLE_LINE_OPERATION) {
			entity.getPrimaryKey().setClientId(entity.getClientId());
			entity.getPrimaryKey().setTableName(entity.getTableName());
			entity.getPrimaryKey().setPropertyType(PropertyType.NUMBER);
			entity.getPrimaryKey().setFieldLength(10);
			entity.getPrimaryKey().setUnique(true);
			entityPropertyDao.add(entity.getPrimaryKey());
		}

		logger.debug("Result: " + result.name());
		logger.debug("END addEntity");

		return result == DBOperationResult.SUCCESS_SINGLE_LINE_OPERATION;
	}

	private String findAvailableTableName(final int clientId, final String defaultName) {
		Integer index = null;
		String nameWithoutIndex = "te_" + clientId + "_" + defaultName + "_";
		do {
			final SearchCriteria criteria = new SearchCriteria();
			criteria.addPropertyCriteria("clientId", clientId);
			criteria.addPropertyCriteria("tableName", nameWithoutIndex + (index == null ? "" : index.intValue()));
			final SearchResultSet<EntityModel> resultSet = searchForEntities(criteria);
			if (resultSet.getTotalResultSize() == 0) {
				return nameWithoutIndex + (index == null ? "" : index.intValue());
			}
			if (index == null) {
				index = 1;
			} else {
				index++;
			}
		} while (true);
	}

	/*
	 * (non-Javadoc)
	 * @see com.test.mapper.services.EntityService#updateEntity(com.test.mapper.enitites.EntityModel)
	 */
	public boolean updateEntity(final EntityModel entity) {
		logger.debug("START updateEntity");

		if (entity == null) {
			throw new NullPointerException("Cannot update NULL entity");
		}
		entity.setEntityName(EntitiesUtils.removeDuplicateSpaces(entity.getEntityName()));
		final boolean nameAvailable = isEntityNameAvailable(entity.getClientId(),
				entity.getEntityName(), entity.getTableName());
		if (!nameAvailable) {
			throw new DuplicateEntityNameException("Entity name "
					+ entity.getEntityName() + " already exists");
		}

		final DBOperationResult result = entityDao.update(entity);
		final EntityProperty pk = entityPropertyDao.load(new EntityPropertyPK(
				entity.getClientId(), entity.getTableName(),
				entity.getPrimaryKey().getColumnName()));
		if (pk != null) {
			logger.debug("Updating primary key...");
			entityPropertyDao.update(entity.getPrimaryKey());
		} else {
			logger.debug("Adding new primary key...");
			entity.getPrimaryKey().setClientId(entity.getClientId());
			entity.getPrimaryKey().setTableName(entity.getTableName());
			entityPropertyDao.add(entity.getPrimaryKey());
		}

		logger.debug("Result: " + result.name());
		logger.debug("END updateEntity");

		return result == DBOperationResult.SUCCESS_SINGLE_LINE_OPERATION;
	}

	/*
	 * (non-Javadoc)
	 * @see com.test.mapper.services.EntityService#removeEntity(com.test.mapper.enitites.EntityModel)
	 */
	public boolean removeEntity(final EntityModel entity) {
		logger.debug("START removeEntity");

		final DBOperationResult result = entityDao.remove(entity);

		logger.debug("Result: " + result.name());
		logger.debug("END removeEntity");

		return result == DBOperationResult.SUCCESS_SINGLE_LINE_OPERATION;
	}

	/*
	 * (non-Javadoc)
	 * @see com.test.mapper.services.EntityService#getEntityByTableName(int, java.lang.String)
	 */
	public EntityModel getEntityByTableName(final int clientId, final String tableName) {
		logger.debug("START getEntityByTableName");

		final EntityModel result = entityDao.load(new EntityModelPK(clientId, tableName));
		if (result != null && result.getPrimaryKey() != null) {
			logger.debug("Loading primary key, column name " + result.getPrimaryKey().getColumnName());

			result.setPrimaryKey(entityPropertyDao.load(
					new EntityPropertyPK(clientId, tableName, result.getPrimaryKey().getColumnName())));
		}
		if (result != null) {
			logger.debug("Loading properties...");
			loadEntityProperties(result);
		}

		logger.debug("Result: " + result);
		logger.debug("END getEntityByTableName");

		return result;
	}

	/*
	 * (non-Javadoc)
	 * @see com.test.mapper.services.EntityService#searchForEntities(com.test.dao.SearchCriteria)
	 */
	public SearchResultSet<EntityModel> searchForEntities(final SearchCriteria criteria) {

		logger.debug("START searchForEntities");
		logger.debug("Items per page: " + criteria.getItemsPerPage());
		logger.debug("Page number: " + criteria.getPageNumber());

		final Collection<EntityModel> resultList = entityDao.search(criteria.getProperties());

		logger.debug("Total number of items found: " + resultList.size());

		final SearchResultSet<EntityModel> result = new SearchResultSet<EntityModel>();
		final SearchUtils.SearchResultIndex indexes = SearchUtils.calculateIndexes(
				resultList.size(), criteria.getPageNumber(), criteria.getItemsPerPage());
		if (resultList.size() == 0) {
			result.setResultList(resultList);
		} else {
			result.setResultList(new ArrayList<EntityModel>(resultList).subList(indexes.getStartIndex(), resultList.size()));
		}
		for (EntityModel model : result.getResultList()) {
			if (model == null) {
				continue;
			}
			model.setPrimaryKey(entityPropertyDao.load(new EntityPropertyPK(
					model.getClientId(), model.getTableName(),
					model.getPrimaryKey().getColumnName())));
		}
		result.setTotalResultSize(resultList.size());

		logger.debug("Items returned: " + result.getResultList().size());
		logger.debug("END searchForEntities");

		return result;
	}

	/*
	 * (non-Javadoc)
	 * @see com.test.mapper.services.EntityService#isEntityNameAvailable(int, java.lang.String, java.lang.String)
	 */
	public boolean isEntityNameAvailable(final int clientId, final String entityName,
			final String existingTableName)
	{
		if (clientId <= 0) {
			throw new IllegalArgumentException("A valid client ID must be specified");
		}
		if (StringUtils.isEmpty(entityName)) {
			throw new IllegalArgumentException("A non-empty entity name must be specified");
		}
		if (existingTableName != null && StringUtils.isBlank(existingTableName)) {
			throw new IllegalArgumentException("Table name must be null or non-blank");
		}
		final SearchCriteria criteria = new SearchCriteria();
		criteria.addPropertyCriteria("clientId", clientId);
		criteria.addPropertyCriteria("entityName", entityName);
		final SearchResultSet<EntityModel> resultSet = searchForEntities(criteria);
		if (resultSet.getTotalResultSize() == 0) {
			return true;
		}
		if (existingTableName != null && resultSet.getTotalResultSize() == 1
				&& resultSet.getResultList().iterator().next().getTableName().trim().equalsIgnoreCase(existingTableName.trim())) {
			return true;
		}
		return false;
	}

	/*
	 * (non-Javadoc)
	 * @see com.test.mapper.services.EntityService#isEntityNameAvailable(int, java.lang.String)
	 */
	public boolean isEntityNameAvailable(final int clientId, final String entityName) {
		return isEntityNameAvailable(clientId, entityName, null);
	}

	/*
	 * (non-Javadoc)
	 * @see com.test.mapper.services.EntityService#loadEntityProperties(com.test.mapper.enitites.EntityModel)
	 */
	public void loadEntityProperties(final EntityModel entity) {
		if (entity == null) {
			throw new NullPointerException("Cannot load properties for NULL entity");
		}

		logger.debug("START loadEntityProperties");
		logger.debug("Client ID: " + entity.getClientId());
		logger.debug("Table name: " + entity.getTableName());

		if (entity.getClientId() == null || entity.getClientId() <= 0) {
			throw new IllegalArgumentException("Invalid client ID: " + entity.getClientId());
		}
		if (StringUtils.isBlank(entity.getTableName())) {
			throw new IllegalArgumentException("Table name is empty");
		}

		final SearchCriteria criteria = new SearchCriteria();
		criteria.setItemsPerPage(0);
		criteria.addPropertyCriteria("clientId", entity.getClientId());
		criteria.addPropertyCriteria("tableName", entity.getTableName());
		entity.setProperties(entityPropertyDao.search(criteria.getProperties()));

		// filter out primary key
		final Iterator<EntityProperty> it = entity.getProperties().iterator();
		while (it.hasNext()) {
			if (entity.getPrimaryKey().getColumnName().equalsIgnoreCase(it.next().getColumnName())) {
				it.remove();
				break;
			}
		}

		logger.debug("END loadEntityProperties");
	}

	public EntityDao getEntityDao() {
		return entityDao;
	}

	public void setEntityDao(EntityDao entityDao) {
		this.entityDao = entityDao;
	}

	public Logger getLogger() {
		return logger;
	}

	public void setLogger(Logger logger) {
		this.logger = logger;
	}

	public EntityPropertyDao getEntityPropertyDao() {
		return entityPropertyDao;
	}

	public void setEntityPropertyDao(EntityPropertyDao entityPropertyDao) {
		this.entityPropertyDao = entityPropertyDao;
	}

	public EntityPropertyService getEntityPropertyService() {
		return entityPropertyService;
	}

	public void setEntityPropertyService(EntityPropertyService entityPropertyService) {
		this.entityPropertyService = entityPropertyService;
	}
}
